#pragma once

#include "pair.hpp"
#include "type_traits.hpp"

namespace zuck
{// a simple tuple class 
	template<zuck::size_t __idx, typename Tuple>
	struct tuple_elements;

	template<zuck::size_t __idx, typename Tuple>
	struct tuple_elements<__idx, const Tuple> : tuple_elements<__idx, Tuple>
	{
		using _Base = tuple_elements<__idx, Tuple>;
		using type = zuck::add_const_t<typename _Base::_My_type>;
	};

	template<zuck::size_t __idx, typename Tuple>
	using tuple_element_t = typename tuple_elements<__idx, Tuple>::type;

	template<typename T>
	struct __tuple_val {
		constexpr __tuple_val(const T& val = T{}) : _m_val(val) {}

		constexpr __tuple_val(T&& val)noexcept : _m_val(val)
		{
			val.~T();
		}

	public:
		constexpr __tuple_val& operator=(const __tuple_val& other)
		{
			_m_val = other._m_val;
			return *this;
		}

		constexpr __tuple_val& operator=(__tuple_val&& other)noexcept
		{
			_m_val = other._m_val;
			other._m_val.~T();
			return *this;
		}

		T _m_val;
	};


	template<typename ...Types>
	class tuple;

	template<>// empty type list
	class tuple<> {
	public:
		constexpr tuple()noexcept = default;

		constexpr tuple(const tuple&) = default;

		constexpr tuple(tuple&&)noexcept = default;

		constexpr tuple& operator=(const tuple&)noexcept { return *this; }

		constexpr tuple& operator=(tuple&&)noexcept { return *this; }

		constexpr void swap(tuple&) {}

		[[nodiscard]] constexpr bool equal()const noexcept { return true; }
	};


	template<typename Mytype, typename ...Rest>
	class tuple<Mytype, Rest...> : private tuple<Rest...>
	{
	private:
		__tuple_val<Mytype> _m_Mytuple_val;
	private:
		using _My_Base = tuple<Rest...>;
		using _My_tuple = __tuple_val<Mytype>;
	public:
		using my_type = Mytype;
	public:
		constexpr tuple() : _My_Base(), _m_Mytuple_val() {}

		template<typename MyType, typename ...Rests>
		constexpr tuple(const MyType& myValue, Rest&&... rests)
			: _My_Base(zuck::forward<Rests>(rests)...), _m_Mytuple_val(zuck::forward<MyType>(myValue)) {}

		template<typename MyType, typename ...Rests>
		constexpr tuple(MyType&& arg, Rests&&... rest)
			: _My_Base(zuck::forward<Rests>(rest)...), _m_Mytuple_val(zuck::move(arg)) {}

		template<typename This, typename ...Others>
		constexpr tuple(const tuple<This, Others...>& other)
			: _My_Base(zuck::forward<tuple<Others>>(other)...), _m_Mytuple_val(zuck::move(other.__get_first())) {}

		template<typename This, typename ...Others>
		constexpr tuple(tuple<This, Others...>&& other)noexcept
			: _My_Base(zuck::forward<tuple<Others>>(other)...), _m_Mytuple_val(zuck::move(other.__get_first())) {}

		constexpr tuple(const tuple&) = default;
		constexpr tuple(tuple&&) = default;

	public:
		[[nodiscard]] constexpr bool equal(const tuple& other)const noexcept
		{
			return __equal_my_self(other._m_val) && _My_Base::equal(other.__get_rest());
		}

		constexpr void swap(tuple& other)noexcept
		{
			__swap(other._m_Mytuple_val);
			_My_Base::swap(other.__get_rest());
		}

	public:
		constexpr tuple& operator=(const tuple& other)
		{
			__copy_assign(other);
			_My_Base::operator=(other.__get_rest());
			return *this;
		}

		constexpr tuple& operator=(tuple&& other)noexcept
		{
			__move_assign(zuck::move(other));
			_My_Base::operator=(zuck::move(other.__get_rest()));
			return *this;
		}

	private:
		[[nodiscard]] constexpr my_type& __get_first()noexcept
		{
			return _m_Mytuple_val._m_val;
		}

		[[nodiscard]] constexpr const my_type& __get_first()const
		{
			return _m_Mytuple_val._m_val;
		}

		constexpr void __copy_assign(const tuple& other)
		{
			_m_Mytuple_val = other._m_Mytuple_val;
		}

		constexpr void __move_assign(tuple&& other)
		{
			_m_Mytuple_val = other._m_Mytuple_val;
			other._m_Mytuple_val = my_type{};
		}

		[[nodiscard]] constexpr bool __equal_my_self(_My_tuple& other)const
		{
			return static_cast<bool>(_m_Mytuple_val == other._m_Mytuple_val);
		}

		constexpr _My_Base& __get_rest()noexcept
		{
			return *this;
		}

		constexpr const _My_Base& __get_rest()const noexcept
		{
			return *this;
		}

		constexpr void __swap(_My_tuple& __t)
		{
			auto __tmpTuple = static_cast<_My_tuple&&>(_m_Mytuple_val);
			_m_Mytuple_val = static_cast<_My_tuple&&>(__t);
			__t = static_cast<_My_tuple&&>(__tmpTuple);
		}

	public:
		template<zuck::size_t __idx, typename ...Types>
		friend constexpr tuple_element_t<__idx, tuple<Types...>>& get(tuple<Types...>&)noexcept;

		template<zuck::size_t __idx, typename ...Types>
		friend constexpr const tuple_element_t<__idx, tuple<Types...>>& get(const tuple<Types...>&)noexcept;
	};

	/**
	* @brief: template class tuple deduction
	*/
	template<typename Mytype, typename...Rests>
	tuple(const Mytype&, Rests&&...) -> tuple<Mytype, Rests...>;

	template<typename Mytype, typename...Rests>
	tuple(Mytype&&, Rests&&...) -> tuple<Mytype, Rests...>;

	template<typename Mytype, typename...Rests>
	tuple(const tuple<Mytype, Rests...>&) -> tuple<Mytype, Rests...>;

	template<typename Mytype, typename...Rests>
	tuple(tuple<Mytype, Rests...>&&) -> tuple<Mytype, Rests...>;

	/**
	* @brief: return checked type
	*/
	template<zuck::size_t __idx>
	struct tuple_elements<__idx, tuple<>> {
		static_assert(zuck::always_false<zuck::integral_constant<zuck::size_t, __idx>>,
			"tuple out of bound");
	};

	template<typename My_type, typename ...Rest>
	struct tuple_elements<0, tuple<My_type, Rest...>> {
		using type = My_type;
		using tuple_type = tuple<My_type, Rest...>;
	};

	template<zuck::size_t __idx, typename My_type, typename ...Rest>
	struct tuple_elements<__idx, tuple<My_type, Rest...>>
		: public tuple_elements<__idx - 1, tuple<Rest...>> {};


	template<zuck::size_t __idx, typename ...Types>
	[[nodiscard]] constexpr zuck::tuple_element_t<__idx, zuck::tuple<Types...>>&
		get(zuck::tuple<Types...>&tuple)noexcept
	{
		using _My_tuple_type = typename zuck::tuple_elements<__idx, zuck::tuple<Types...>>::tuple_type;
		return static_cast<_My_tuple_type&>(tuple).__get_first();
	}

	template<zuck::size_t __idx, typename ...Types>
	[[nodiscard]] constexpr const typename zuck::tuple_element_t<__idx, zuck::tuple<Types...>>&
		get(const zuck::tuple<Types...>&tuple)noexcept
	{
		using _My_tuple_type = typename zuck::tuple_elements<__idx, zuck::tuple<Types...>>::tuple_type;
		return static_cast<const _My_tuple_type&>(tuple).__get_first();
	}
}

